/********************************************************/
/*	Author:		gong_libin			*/
/*	Date:		2015_02_07			*/
/*	File:		GtDcpDns.cpp			*/
/********************************************************/

#include "GtDcpDns.h"
#include "GtShm.h"
#include <linux/filter.h>

static UINT sg_uiMsg;
static GtIpc::CGtMsg* sg_pCMsg;
static GTDCPSHMDEVS_S* sg_pDns;
static GtIpc::CGtShm	sg_pCShm;

int main(int argc, char* argv[])
{
	int iTotal = 1;
	int iCount = 0;
 	int iIndex = 0;
	int iReturn = 0;
	char* pszBind = NULL;
	GtNet::CGtRaw CRaw;
	GtNet::CGtDgram CDgram;
	GtNet::CGtNet* pCNet = NULL;
	GtCls::CGtThread* pCThread = NULL;

	struct option stOpt[] = {
		{"bind", 1, 0, 'b'},
		{"thread", 1, 0, 't'},
		{"version", 0, 0, 'v'},
		{"help", 0, 0, 'h'},
		{0, 0, 0, 0},
	};

	while (GT_FAILURE != (iReturn = getopt_long(argc, argv, "b:t:vh", stOpt, &iIndex))) {
		switch (iReturn) {
			case 'b':
				pszBind = optarg;
				break;
			case 't':
				iTotal = atoi(optarg);
				break;
			case 'v':
				GT_PRINT("GtDns Build on %s %s.\n", __DATE__, __TIME__);
				return GT_SUCCESS;
			case 'h':
				GT_PRINT("Usage: %s [options]\n", argv[0]);
				GT_PRINT("Valid options are:\n");
				GT_PRINT("[0] -b or --bind:       set bind inet|unix\n");
				GT_PRINT("[0] -t or --thread:     set thread total\n");
				GT_PRINT("[0] -v or --version:    show version.\n");
				GT_PRINT("[0] -h or --help:       show help.\n");
				GT_PRINT("GtDns Build on %s %s.\n", __DATE__, __TIME__);
				return GT_SUCCESS;
			default:
				return GT_SUCCESS;
		}
	}

	#ifdef _GT_DEBUG_
		iTotal = 1;
	#else
		GtFork();
	#endif

	signal(SIGINT, GtDnQuit);
	signal(SIGSEGV, GtDnQuit);
	signal(SIGTERM, GtDnQuit);
	
	/* msg */
	GT_PRINT("Will CMsg.GtIpcCreate...\n");
	sg_pCMsg = new GtIpc::CGtMsg[GT_DD_MSG];
	GtIpcMsgKernel(GT_DD_MAX, GT_DD_MNB, GT_DD_MNI);
	for (iCount = 0; iCount < GT_DD_MSG; iCount ++) {
		if (GT_SUCCESS != (sg_pCMsg + iCount)->GtIpcCreate((sg_pCMsg + iCount)->GtIpcKey(GT_DD_PATH, GT_DD_KEY + iCount))) {
			GT_ERROR("Failed to GtIpcCreate\n");
			return GT_FAILURE;
		}
	}

	/* bind */
	if (0x2f != *pszBind) {
		pCNet = &CRaw;
		GT_PRINT("Will GtDnInet...\n");
		iReturn = GtDnInet(pszBind, pCNet);
	}
	else {
		pCNet = &CDgram;
		GT_PRINT("Will GtDnUnix...\n");
		iReturn = GtDnUnix(pszBind, pCNet);
	}

	/* thread */
	if (GT_SUCCESS == iReturn) {
		
		/*statistics Init*/
		GTDCPSHMDEVS_S* pstTemp = NULL;
		if (GT_SUCCESS == sg_pCShm.GtIpcCreate(sg_pCShm.GtIpcKey(GT_DD_CTRL, GT_DD_KDNS), GT_DD_CTTL * sizeof(GTDCPSHMDEVS_S))) {
			sg_pDns = (GTDCPSHMDEVS_S*)sg_pCShm.GtShmGetShm();
			pstTemp  = sg_pDns + GT_DD_CTTL;
			while (sg_pDns  < pstTemp) {
				if(0 == sg_pDns->m_stInfo.m_szPid[0]){
					sprintf(sg_pDns->m_stInfo.m_szPid, "%d", getpid());
					strcpy(sg_pDns->m_stInfo.m_szDev, pszBind);
					break;
				}
				sg_pDns++;
			}
			if(sg_pDns == pstTemp){
				GT_PRINT("Failed to GtIpcCreate\n");
				return GT_FAILURE;
			}
		}
		else {
			GT_PRINT("Failed to GtIpcCreate\n");
			return GT_FAILURE;
		}

		pCThread = new GtCls::CGtThread[iTotal];
		GT_PRINT("Will CThread.GtThreadCreate...\n");
		for (iCount = 0; iCount < iTotal; iCount ++) {
			(pCThread + iCount)->GtThreadAttrInit();
			(pCThread + iCount)->GtThreadRoutineSet(GtDnCore);
			(pCThread + iCount)->GtThreadCreate(pCNet);
		}
		for (iCount = 0; iCount < iTotal; iCount ++) {
			(pCThread + iCount)->GtThreadJoin(NULL);
		}
	}
	else {
		GT_ERROR("Failed to GtDnBind\n");
	}

	return GT_SUCCESS;
}

void GtDnQuit(int iSignal)
{
	memset(sg_pDns, 0, sizeof(GTDCPSHMDEVS_S));
	GT_ERROR("GtDcpDns-1.1.0 stopped by signal %d\n", iSignal);

	exit(GT_FAILURE);
}

int GtDnInet(char* pszBind, GtNet::CGtNet* pCNet)
{
	struct ifreq stIfreq;
	struct sockaddr_ll stAddr;
	GtNet::CGtRaw* pCRaw = (GtNet::CGtRaw*)pCNet;

	struct sock_filter stBfpFilter[] =
    {
		{ 0x28, 0, 0, 0x0000000c },
		{ 0x15, 0, 6, 0x000086dd },
		{ 0x30, 0, 0, 0x00000014 },
		{ 0x15, 2, 0, 0x00000084 },
		{ 0x15, 1, 0, 0x00000006 },
		{ 0x15, 0, 13, 0x00000011 },
		{ 0x28, 0, 0, 0x00000038 },
		{ 0x15, 10, 11, 0x00000035 },
		{ 0x15, 0, 10, 0x00000800 },
		{ 0x30, 0, 0, 0x00000017 },
		{ 0x15, 2, 0, 0x00000084 },
		{ 0x15, 1, 0, 0x00000006 },
		{ 0x15, 0, 6, 0x00000011 },
		{ 0x28, 0, 0, 0x00000014 },
		{ 0x45, 4, 0, 0x00001fff },
		{ 0xb1, 0, 0, 0x0000000e },
		{ 0x48, 0, 0, 0x00000010 },
		{ 0x15, 0, 1, 0x00000035 },
		{ 0x6, 0, 0, 0x0000ffff },
		{ 0x6, 0, 0, 0x00000000 },
    };
    struct sock_fprog stFilter;
    stFilter.len = sizeof(stBfpFilter)/sizeof(stBfpFilter[0]);
    stFilter.filter = stBfpFilter;

	if (GT_SUCCESS == pCRaw->GtRawInitRecv(GT_PF_PACKET, GT_SOCK_RAW, htons(GT_ETH_P_ALL))) {
		strcpy(stIfreq.ifr_name, pszBind);
		if (GT_SUCCESS == ioctl(pCRaw->GtNetGetSocket(), SIOCGIFINDEX, &stIfreq)) {
			setsockopt(pCRaw->GtNetGetSocket(), SOL_SOCKET, SO_ATTACH_FILTER, &stFilter, sizeof(stFilter));
			stAddr.sll_family = GT_PF_PACKET;
			stAddr.sll_ifindex = stIfreq.ifr_ifindex;
			stAddr.sll_protocol = htons(GT_ETH_P_ALL);
			if (GT_SUCCESS != pCRaw->GtNetBind(&stAddr, sizeof(stAddr))) {
				goto GtError;
			}
		}
		else {
			goto GtError;
		}
	}
	else {
		goto GtError;
	}

	return GT_SUCCESS;

GtError:
	GT_ERROR("%s\n", strerror(errno));
	pCRaw->GtNetClose();

	return GT_FAILURE;
}

int GtDnUnix(char* pszBind, GtNet::CGtNet* pCNet)
{
	unlink(pszBind);

	return pCNet->GtNetInitRecv(pszBind);
}
void GtDnDnsDisplay(struct tagGtDnDns *stDns)
{
	int i = 0, len = 0;
	GT_PRINT("QU: %d, AN: %d, AU: %d, AD: %d\n", 
		stDns->m_usQuNum,stDns->m_usAnNum,stDns->m_usAuNum,stDns->m_usAdNum);
	for(i = 0; i < GT_DN_QUES; i++)
	{
		len =  strlen((const char *)stDns->m_stQuery[i].m_szHost);
		if(len > 0)
			GT_PRINT("Host: %s, Type: %d, Class: %d\n", 
				stDns->m_stQuery[i].m_szHost,stDns->m_stQuery[i].m_usType,stDns->m_stQuery[i].m_usClass);
		else
			return;
	}
	return;
}

void* GtDnCore(void* pNet)
{
	int iLength = 0;
	GTDNDNS_S stDns;
	struct udphdr* pstUdp = NULL;
	UCHAR uszPacket[GT_KBYTES8] = { 0 };
	GtNet::CGtNet* pCNet = (GtNet::CGtNet*)pNet;

	struct ip* const c_pstIP = (struct ip*)(uszPacket + GT_DR_ETHLEN);

	#ifdef _GT_DEBUG_
		GtCls::CGtTime CTime;
	#endif
	GTDCPSHMBASE_S* pstPktInfo = &sg_pDns->m_stBase;
	while ((iLength = pCNet->GtNetRecv(uszPacket, GT_KBYTES8)) > 0) {
		if (IPPROTO_UDP == c_pstIP->ip_p) {
			#ifdef _GT_DEBUG_
				CTime.GtTimeOn();
			#endif
							
			__sync_add_and_fetch(&pstPktInfo->m_ullRcvPkts, 1);
			__sync_add_and_fetch(&pstPktInfo->m_ullRcvByte, iLength);
			
			pstUdp = (struct udphdr*)((UCHAR *)c_pstIP + (c_pstIP->ip_hl<<2));
			if (GT_DN_PORT == ntohs(pstUdp->uh_dport)) {
				#ifdef _GT_DEBUG_
					GT_PRINT("\n");
					GT_DEBUG("GtDnCore[%lu] start\n", pthread_self());
					GT_DEBUG("SrcAddr: %s, ", inet_ntoa(c_pstIP->ip_src));
					GT_PRINT("DstAddr: %s\n", inet_ntoa(c_pstIP->ip_dst));
					GT_DEBUG("Protocol: UDP, SrcPort: %d, DstPort: %d\n", ntohs(pstUdp->uh_sport), ntohs(pstUdp->uh_dport));
				#endif
				
				memset(&stDns, '\0', sizeof(GTDNDNS_S));

				strncpy((char *)stDns.m_szSaddr, inet_ntoa(c_pstIP->ip_src), GT_BYTE16);
				strncpy((char *)stDns.m_szDaddr, inet_ntoa(c_pstIP->ip_dst), GT_BYTE16);
				sprintf((char *)stDns.m_szSport, "%d", ntohs(pstUdp->uh_sport));
				sprintf((char *)stDns.m_szDport, "%d", ntohs(pstUdp->uh_dport));
				
				if( GT_SUCCESS == GtDnDnsParse(&stDns, (UCHAR *)(pstUdp + 1), iLength - (c_pstIP->ip_hl<<2) - sizeof(struct udphdr) - GT_DR_ETHLEN))
				{
					#ifdef _GT_DEBUG_
						GtDnDnsDisplay(&stDns);
					#endif

					if( 0 == GtDnQueue(&stDns))
					{
						__sync_add_and_fetch(&pstPktInfo->m_ullSndPkts, 1);
						__sync_add_and_fetch(&pstPktInfo->m_ullSndByte, iLength);					
						#ifdef _GT_DEBUG_
							GT_DEBUG("GtDnQueue Success!\n");
						#endif
					}else
					{
						#ifdef _GT_DEBUG_
							GT_DEBUG("GtDnQueue Faild!\n");
						#endif
						__sync_add_and_fetch(&pstPktInfo->m_ullQueErrs, 1);
					}

					
				}else
				{
					__sync_add_and_fetch(&pstPktInfo->m_ullParseErrs, 1);
					__sync_add_and_fetch(&pstPktInfo->m_ullParseErrsByte, iLength);
					
				}
				#ifdef _GT_DEBUG_
					GT_DEBUG("GtDnCore[%lu] over Use time ", pthread_self());
					CTime.GtTimeOff();
					CTime.GtTimeDisplay();
				#endif

				
			}
			else {
				/* GT_DR_PORT != ntohs(pstUdp->uh_dport) */
				__sync_add_and_fetch(&pstPktInfo->m_ullDrpPkts, 1);
				__sync_add_and_fetch(&pstPktInfo->m_ullDrpByte, iLength);
			}
		}
		else {
			/* IPPROTO_UDP != c_pstIP->ip_p */
		}
	}

	pthread_exit(NULL);
}

int GtDnDnsParse(GTDNDNS_S* pstDns, UCHAR* puszDns, int iDns)
{
	UCHAR ucOffset = 0;
	USHORT usCount = 0;
	int len = 0;
	int result = GT_SUCCESS;

	if(iDns < 13)
		return GT_FAILURE;
	
	/* id */
	pstDns->m_usID = ntohs(*(USHORT*)puszDns);
	puszDns += sizeof(USHORT);

	/* flags */
	pstDns->m_stFlag.m_ucQR = ntohs(*(USHORT*)puszDns) & GT_DNS_FGQR;
	pstDns->m_stFlag.m_ucOP = ntohs(*(USHORT*)puszDns) & GT_DNS_FGOP;
	pstDns->m_stFlag.m_ucAA = ntohs(*(USHORT*)puszDns) & GT_DNS_FGAA;
	pstDns->m_stFlag.m_ucTC = ntohs(*(USHORT*)puszDns) & GT_DNS_FGTC;
	pstDns->m_stFlag.m_ucRD = ntohs(*(USHORT*)puszDns) & GT_DNS_FGRD;
	pstDns->m_stFlag.m_ucRA = ntohs(*(USHORT*)puszDns) & GT_DNS_FGRA;
	pstDns->m_stFlag.m_ucZR = 0;
	pstDns->m_stFlag.m_ucRC = ntohs(*(USHORT*)puszDns) & GT_DNS_FGRC;
	puszDns += sizeof(USHORT);

	/* count */
	pstDns->m_usQuNum = ntohs(*(USHORT*)puszDns);
	puszDns += sizeof(USHORT);
	pstDns->m_usAnNum = ntohs(*(USHORT*)puszDns);
	puszDns += sizeof(USHORT);
	pstDns->m_usAuNum = ntohs(*(USHORT*)puszDns);
	puszDns += sizeof(USHORT);
	pstDns->m_usAdNum = ntohs(*(USHORT*)puszDns);
	puszDns += sizeof(USHORT);

	/* Only parse request message */
	if(pstDns->m_stFlag.m_ucQR != 0)
		return GT_FAILURE;
		
	/* Malformed Packet: DNS */
	if((pstDns->m_usQuNum > GT_DN_QUES) || pstDns->m_stFlag.m_ucRC != 0)
		return GT_FAILURE;
	
	iDns -= 12;
	
	/* query */
	while (usCount < pstDns->m_usQuNum) 
	{
		ucOffset = *puszDns;

		if(iDns>0 && ucOffset < 64 && len+ucOffset < 256)
		{
			if(len>0)
				pstDns->m_stQuery[usCount].m_szHost[len++]='.';
			strncpy(&pstDns->m_stQuery[usCount].m_szHost[len],(char *)(puszDns+1),(UINT)ucOffset);
			len += ucOffset;
		}
		else
		{
			result = GT_FAILURE;
			break;
		}
		
		puszDns += ucOffset+1;
		ucOffset = *puszDns;
		if(!ucOffset)
		{
			pstDns->m_stQuery[usCount].m_usType = ntohs(*(USHORT*)(puszDns+1));
			if((pstDns->m_stQuery[usCount].m_usType>16 && pstDns->m_stQuery[usCount].m_usType<252) 
				|| pstDns->m_stQuery[usCount].m_usType>255)
			{
				result = GT_FAILURE;
				break;
			}
			pstDns->m_stQuery[usCount].m_usClass = ntohs(*(USHORT*)(puszDns+3));
			if(pstDns->m_stQuery[usCount].m_usClass>4 && pstDns->m_stQuery[usCount].m_usClass!=255 )
			{
				result = GT_FAILURE;
				break;
			}
			
			iDns -= (len+6);
			len = 0;
			usCount += 1;
			puszDns += 5;
		}
	}
	return result;
}

int GtDnQueue(GTDNDNS_S* pstDns)
{
	int len = 0;
	GTDCPMSG_S stMsg;
	USHORT usCount = 0;
	GtNet::CGtPro CPro;
	GtNet::GTPROKV_S stCont[GT_DN_QUES] = {{'\0', 0, NULL}};
	GtNet::GTPROKV_S stPair[] = {
		{GT_DD_SADD, strlen((const char *)pstDns->m_szSaddr), (UCHAR*)pstDns->m_szSaddr},
		{GT_DD_DADD, strlen((const char *)pstDns->m_szDaddr), (UCHAR*)pstDns->m_szDaddr},
		{GT_DD_SPOT, strlen((const char *)pstDns->m_szSport), (UCHAR*)pstDns->m_szSport},
		{GT_DD_DPOT, strlen((const char *)pstDns->m_szDport), (UCHAR*)pstDns->m_szDport},
	};
	GtNet::GTPROHEAD_S stHead = {
		GT_DD_FLAG,
		GT_PRO_0,
		GT_PRO_DATA,
		GT_PRO_0,
		GT_PRO_N,
		GT_PRO_0,
		GT_DD_STDS,
		GT_DD_VDNS,
		GT_PRO_0,
		GT_DEF_SUCC,
		GT_PRO_0,
		GT_PRO_0,
	};

	while (usCount < GT_DN_QUES) {
		len = strlen((const char *)pstDns->m_stQuery[usCount].m_szHost);
		if (len != 0) {
			stCont[usCount].m_ucKey = GT_DD_HOST;
			stCont[usCount].m_iLength = len;
			stCont[usCount].m_puszValue = (UCHAR*)pstDns->m_stQuery[usCount].m_szHost;
		}
		else {
			break;
		}
		usCount += 1;
	}

	stHead.m_usTotal = sizeof(stPair) / sizeof(GtNet::GTPROKV_S) + usCount;
	stHead.m_iLength += GtProKvOutput(stPair, sizeof(stPair) / sizeof(GtNet::GTPROKV_S), stMsg.m_uszMsg + GT_PRO_HEAD, GT_KBYTES4 - GT_PRO_HEAD);
	stHead.m_iLength += GtProKvOutput(stCont, usCount, stMsg.m_uszMsg + GT_PRO_HEAD + stHead.m_iLength, GT_KBYTES4 - GT_PRO_HEAD - stHead.m_iLength);

	CPro.GtProInduce(&stHead);
	CPro.GtProCreate(stMsg.m_uszMsg, GT_PRO_HEAD);

	stMsg.m_iLength = GT_PRO_HEAD + stHead.m_iLength;
	return (sg_pCMsg + (__sync_add_and_fetch(&sg_uiMsg, 1) % GT_DD_MSG))->GtMsgPut((void*)&stMsg, sizeof(GTDCPMSG_S), GT_IPC_NOWAIT);

}

